package com.myblockchain.service.impl;

import com.alibaba.fastjson.JSON;
import com.myblockchain.service.BlockService;
import com.myblockchain.service.P2PService;
import com.myblockchain.service.WalletService;
import com.myblockchain.service.model.Message;
import com.myblockchain.service.model.Block;
import com.myblockchain.service.model.Transaction;
import com.myblockchain.service.model.Wallet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


import javax.websocket.Session;
import java.util.*;

/**
 * @author Liu
 */
@Service
public class P2PServiceImpl implements P2PService {
    /**
     * 查询最新的区块
     */
    private final static int QUERY_LATEST_BLOCK = 0;
    /**
     * 查询整个区块链
     */
    private final static int QUERY_BLOCK_CHAIN = 1;
    /**
     * 查询交易集合
     */
    private final static int QUERY_TRANSACTION = 2;
    /**
     * 查询已打包的交易集合
     */
    private final static int QUERY_PACKED_TRANSACTION = 3;
    /**
     * 查询钱包集合
     */
    private final static int QUERY_WALLET = 4;
    /**
     * 返回区块集合
     */
    private final static int RESPONSE_BLOCK_CHAIN = 5;
    /**
     * 返回交易集合
     */
    private final static int RESPONSE_TRANSACTION = 6;
    /**
     * 返回已打包交易集合
     */
    private final static int RESPONSE_PACKED_TRANSACTION = 7;
    /**
     * 返回钱包集合
     */
    private final static int RESPONSE_WALLET = 8;

    private List<Session> clients = new ArrayList<>();

    @Autowired
    private BlockService blockService;

    @Autowired
    private WalletService walletService;


    @Override
    public void addClient(Session session) {
        clients.add(session);
    }

    @Override
    public void removeClient(Session session) {
        clients.remove(session);
    }

    @Override
    public void handleMessage(Session session, String msg) {
        Message message = JSON.parseObject(msg, Message.class);
        switch (message.getType()){
            case QUERY_LATEST_BLOCK:
                sendMsg(session, responseLatestBlockMsg());
                break;
            case QUERY_BLOCK_CHAIN:
                sendMsg(session, responseBlockMsg());
                break;
            case QUERY_TRANSACTION:
                sendMsg(session, responseTransactions());
                break;
            case QUERY_PACKED_TRANSACTION:
                sendMsg(session, responsePackedTransactions());
                break;
            case QUERY_WALLET:
                sendMsg(session, responseWallets());
                break;
            case RESPONSE_BLOCK_CHAIN:
                handleBlockChainResponse(message.getData());
                break;
            case RESPONSE_TRANSACTION:
                handleTransactionResponse(message.getData());
                break;
            case RESPONSE_PACKED_TRANSACTION:
                handlePackedTransactionResponse(message.getData());
                break;
            case RESPONSE_WALLET:
                handleWalletResponse(message.getData());
                break;
            default:
        }
    }

    private void handleWalletResponse(String msg) {
        List<Wallet> wallets = "\"[]\"".equals(msg)?new ArrayList<>():JSON.parseArray(msg, Wallet.class);
        wallets.forEach(wallet -> {
            blockService.addOtherWallet(walletService.getWalletAddress(wallet.getPublicKey()),wallet );
        });
    }


    private void handlePackedTransactionResponse(String msg) {

        List<Transaction> txs = "\"[]\"".equals(msg)?new ArrayList<>():JSON.parseArray(msg, Transaction.class);
        blockService.addPackedTransaction(txs);
    }

    private void handleTransactionResponse(String msg) {
        List<Transaction> txs = "\"[]\"".equals(msg)?new ArrayList<>():JSON.parseArray(msg, Transaction.class);
        blockService.addTransaction(txs);
    }

    @Override
    public void sendMsg(Session session, String msg) {

        System.out.println("发送给" + session.getId() + "的p2p消息是 ： " + msg);

        session.getAsyncRemote().sendText(msg);
    }




    public synchronized void handleBlockChainResponse(String msg) {

        List<Block> receiverBlockChain = JSON.parseArray(msg, Block.class);
        Collections.sort(receiverBlockChain, new Comparator<Block>() {
            @Override
            public int compare(Block o1, Block o2) {
                return o1.getIndex() - o2.getIndex();
            }
        });
        Block latestBlockReceived = receiverBlockChain.get(receiverBlockChain.size() - 1);
        Block latestBlock = blockService.getLatestBlock();
        if (latestBlockReceived.getIndex() > latestBlock.getIndex()) {
            if (latestBlock.getHash().equals(latestBlockReceived.getPreviousHash())) {
//                add new received block into local
                if (blockService.addBlock(latestBlockReceived)) {
                    broadcast(responseLatestBlockMsg());
                }
            } else if (receiverBlockChain.size() == 1) {
//                    query all nodes' block chain
                broadcast(queryBlockChainMsg());
            } else {
                // TODO: 2020/2/16 replaceChain
                blockService.replaceChain(receiverBlockChain);
            }
        } else {
            //            received chain length lower than local chain length
            //            do nothing
        }
    }


    @Override
    public void broadcast(String msg) {
        if (clients.size() == 0){
            return;
        }
//        broadcast is starting
        for (Session session : clients) {
            this.sendMsg(session, msg);
        }
//        broadcast is end
    }



    public String responseBlockMsg() {
        return JSON.toJSONString(new Message(RESPONSE_BLOCK_CHAIN, blockService.getBlockChain()));
    }

    public String responseLatestBlockMsg() {
        Block[] blocks = {blockService.getLatestBlock()};
        return JSON.toJSONString(new Message(RESPONSE_BLOCK_CHAIN, JSON.toJSONString(blocks)));
    }

    private String responsePackedTransactions() {
        return JSON.toJSONString(new Message(RESPONSE_PACKED_TRANSACTION,JSON.toJSONString(blockService.findPackedTransactionList())));
    }

    private String responseTransactions() {
        return JSON.toJSONString(new Message(RESPONSE_TRANSACTION,blockService.findTransactions()));
    }

    private String responseWallets() {
        String wallets = blockService.findAllWallets();
        return JSON.toJSONString(new Message(RESPONSE_WALLET, wallets));
    }

    @Override
    public String queryPackedTransactionMsg() {
        return JSON.toJSONString(new Message(QUERY_PACKED_TRANSACTION));
    }

    @Override
    public String queryLatestBlockMsg() {
        return JSON.toJSONString(new Message(QUERY_LATEST_BLOCK));
    }

    @Override
    public String queryTransactionMsg() {
        return JSON.toJSONString(new Message(QUERY_TRANSACTION));
    }

    @Override
    public String queryWalletMsg() {
        return JSON.toJSONString(new Message(QUERY_WALLET));
    }

    @Override
    public void broadcastNewWallet(String msg) {
        broadcast(JSON.toJSONString(new Message(RESPONSE_WALLET, msg)));
    }

    @Override
    public void broadcastNewTransaction(String msg) {
        broadcast(JSON.toJSONString(new Message(RESPONSE_TRANSACTION, msg)));
    }

    @Override
    public void broadcastNewBlock(String msg) {
        broadcast(JSON.toJSONString(new Message(RESPONSE_BLOCK_CHAIN, msg)));
    }

    private String queryBlockChainMsg() {
        return JSON.toJSONString(new Message(QUERY_BLOCK_CHAIN));
    }
}
